/*
* ====================================================================
* Copyright (c) 2013-2014 Neudesic.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that redistributions of source
* retain the above copyright notice, this list of conditions and the
* following disclaimer.
*
* This source file includes software written by Neudesic, LLC (http://www.neudesic.com).
*
* THIS SOFTWARE IS PROVIDED BY Neudesic, LLC ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  FURTHER, IN NO EVENT SHALL
* Neudesic, LLC (THE AUTHOR) OR ANY CONTRIBUTORS BE LIABLE FOR ANY DAMAGES
* WHATSOEVER THAT ARE RELATED TO THE SOFTWARE [INCLUDING BUT NOT LIMITED TO,
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* SUCH AS LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION, HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)] EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGES.
*
* ====================================================================
*/
#include "winrtutils.h"
#include <roerrorapi.h>
#include <wchar.h>
#include <winerror.h>
#include <string>
#include <winstring.h>

#include <windows.networking.sockets.h>
#include <wrl.h>
#include <windows.storage.streams.h>

using ABI::Windows::Networking::Sockets::SocketErrorStatus;
using Microsoft::WRL::Wrappers::HStringReference;
using Microsoft::WRL::ComPtr;

static wchar_t* socketErrors[] =
{
	L"Unknown",
	L"OperationAborted",
	L"HttpInvalidServerResponse",
	L"ConnectionTimedOut",
	L"AddressFamilyNotSupported",
	L"SocketTypeNotSupported",
	L"HostNotFound",
	L"NoDataRecordOfRequestedType",
	L"NonAuthoritativeHostNotFound",
	L"ClassTypeNotFound",
	L"AddressAlreadyInUse",
	L"CannotAssignRequestedAddress",
	L"ConnectionRefused",
	L"NetworkIsUnreachable",
	L"UnreachableHost",
	L"NetworkIsDown",
	L"NetworkDroppedConnectionOnReset",
	L"SoftwareCausedConnectionAbort",
	L"ConnectionResetByPeer",
	L"HostIsDown",
	L"NoAddressesFound",
	L"TooManyOpenFiles",
	L"MessageTooLong",
	L"CertificateExpired",
	L"CertificateUntrustedRoot",
	L"CertificateCommonNameIsIncorrect",
	L"CertificateWrongUsage",
	L"CertificateRevoked",
	L"CertificateNoRevocationCheck",
	L"CertificateRevocationServerOffline",
	L"CertificateIsInvalid"
};

BOOL CallSuccessful(HRESULT hr, LPCWSTR format, ...)
{
	wchar_t buffer[1024];
	va_list args;
	va_start(args, format);
	int len = ::_vscwprintf(format, args) + 1; // _vscprintf doesn't count terminating '\0'
	::vswprintf_s(buffer, min(len, sizeof(buffer)), format, args);
	va_end(args);

	//This code assumes that any non-zero value is an error.
	if (hr)
		::ShowRuntimeMessage(hr, buffer);

	return (hr == 0);
}

std::wstring ConvertToWideString(LPCSTR inString)
{
	std::string s = std::string(inString);
	return std::wstring(s.begin(), s.end());
}

errno_t ConvertToWideString(LPCSTR inString, size_t sizeInWords, PWSTR outString)
{
	errno_t result = 0;
	if (nullptr == inString)
	{
		::ZeroMemory(outString, sizeInWords);
	}
	else
	{
		size_t convertedChars;
		result = ::mbstowcs_s(&convertedChars, outString, sizeInWords, inString, _TRUNCATE);
	}
	return result;
}

errno_t ConvertToMultiByteString(LPCWSTR inString, size_t sizeInBytes, PSTR outString)
{
	errno_t result = 0;
	if (nullptr == inString)
	{
		::ZeroMemory(outString, sizeInBytes);
	}
	else
	{
		size_t convertedChars;
		result = ::wcstombs_s(&convertedChars, outString, sizeInBytes, inString, _TRUNCATE);
	}
	return result;
}

std::wstring ConvertToWideString(const HSTRING* inString)
{
	return std::wstring(::WindowsGetStringRawBuffer(*inString, nullptr));
}

std::string ConvertToMultiByteString(const HSTRING* inString)
{
	std::wstring ws = std::wstring(::WindowsGetStringRawBuffer(*inString, nullptr));
	return std::string(ws.begin(), ws.end());
}

std::string ConvertToMultiByteString(const std::wstring& inString)
{
	return std::string(inString.begin(), inString.end());
}

HString ConvertToHString(const char* inString)
{
	int len = (::strlen(inString) + 1) << 1;	//Need space for wide characters plus end character.
	wchar_t* winString = (wchar_t*)::malloc(len);
	::ConvertToWideString(inString, len, winString);
	HString h = ConvertToHString(winString);
	::free(winString);
	return h;
}

HString ConvertToHString(const wchar_t* inString)
{
	HSTRING hOutString;
	::WindowsCreateString(inString, ::wcslen(inString) + 1, &hOutString);
	HString h;
	h.Attach(hOutString);
	return h;
}

SocketErrorStatus GetSocketErrorStatus(HRESULT error)
{
	ComPtr<ABI::Windows::Networking::Sockets::ISocketErrorStatics> socketErrorStatics;
	SocketErrorStatus status = SocketErrorStatus::SocketErrorStatus_NetworkIsDown;
	HRESULT hr = ABI::Windows::Foundation::GetActivationFactory(HStringReference(RuntimeClass_Windows_Networking_Sockets_SocketError).Get(), &socketErrorStatics);
	if (SUCCEEDED(hr))
	{
		if (FAILED(socketErrorStatics->GetStatus(error, &status)))
			status = SocketErrorStatus::SocketErrorStatus_Unknown;
	}
	return status;
}

void ShowRuntimeMessage(HRESULT hr, LPCWSTR format, ...)
{
	wchar_t buffer[2048];
	wchar_t errorBuffer[200] = L"";
	va_list args;
	va_start(args, format);
	int len = ::_vscwprintf(format, args) + 1; // _vscprintf doesn't count terminating '\0'
	::vswprintf_s(buffer, min(len, sizeof(buffer) - sizeof(errorBuffer)), format, args);
	va_end(args);
	if (hr)
	{
		HRESULT hrCopy = hr;
		if (hrCopy > 0)
			hrCopy = ::HRESULT_FROM_WIN32(hrCopy);
		SocketErrorStatus status = ::GetSocketErrorStatus(hr);
		wchar_t* description = (status < 0 || status > sizeof(socketErrors) / sizeof(wchar_t*)) ? socketErrors[0] : socketErrors[status];
		::swprintf_s(errorBuffer, (sizeof(errorBuffer) / sizeof(wchar_t)), L" Incoming error:[%d] HRESULT:[0x%X] SocketErrorStatus:[%d/%s]",
			hr, hrCopy, status, description);
	}
	::wcscat_s(errorBuffer, (sizeof(errorBuffer) / sizeof(wchar_t)), L"\r\n");
	::wcscat_s(buffer, (sizeof(buffer) / sizeof(wchar_t)), errorBuffer);
	::OutputDebugStringW(buffer);
}

